package com.pvdnc.dxhook;

import android.content.Context;
import android.util.Log;

import java.io.File;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class XHookManager {
    private static final String TAG= XHookManager.class.getSimpleName();

    public static final XHookManager INSTANCE=new XHookManager();

    private final Map<CalleeInfo,Method> mInfoMethodMap=new ConcurrentHashMap<>();

    private final Map<CalleeInfo,HookCallback> mInfoCallbackMap=new ConcurrentHashMap<>();

    public void cacheBackup(Method method){
        CalleeInfo calleeInfo=new CalleeInfo(CalleeInfo.HookMode.REPLACE,method);
        mInfoMethodMap.put(calleeInfo,method);
    }

    public Method getOldMethod(CalleeInfo calleeInfo,Object thisRef) throws NoSuchMethodException {
        Method method= mInfoMethodMap.get(calleeInfo);
        if(method==null){
            throw new IllegalArgumentException("method may has not been hooked yet");
        }
        switch (calleeInfo.getHookMode()){
            case REPLACE:{
                Log.d(TAG,"attempt to call backup method:\n\t"+method);
                method.setAccessible(true);//hook private方法的话，需要重设access_flags
                return method;
            }
            case OVERRIDE:{
                Class<?> cls=thisRef.getClass();//获取hook替换类
                Method superCallerMethod= cls.getDeclaredMethod(DxHook.Override.getSuperCallerMethodName(method),
                        method.getParameterTypes());
                Log.d(TAG,"attempt to call super caller method:\n\t"+superCallerMethod);
                return superCallerMethod;
            }
            default:{
                throw new IllegalArgumentException("method may has not been hooked yet");
            }
        }
    }

    public Object callOldMethod(CalleeInfo calleeInfo,Object thisRef,Object[] args) throws Exception {
        Method method= getOldMethod(calleeInfo,thisRef);
        return method.invoke(thisRef,args);
    }

    public void putCallback(CalleeInfo calleeInfo,HookCallback callback){
        mInfoCallbackMap.put(calleeInfo,callback);
    }

    public HookCallback getCallback(CalleeInfo calleeInfo){
        return mInfoCallbackMap.get(calleeInfo);
    }

    public static void hookMethod(Context context,
                           Method method,HookCallback callback) throws Exception {
        CalleeInfo calleeInfo= DxHook.Replace.createHookClass(context,method);
        XHookManager.INSTANCE.putCallback(calleeInfo,callback);
    }

    public static void hookMethod(ClassLoader parent, File optDir,
                                  Method method,HookCallback callback) throws Exception {
        CalleeInfo calleeInfo=DxHook.Replace.createHookClass(parent,optDir,method);
        XHookManager.INSTANCE.putCallback(calleeInfo,callback);
    }
}
